/*
 * Copyright (C) 2010 Fores Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package jp.fores.midori.server.plugin;

import java.lang.reflect.Modifier;

import javax.servlet.http.HttpServletRequest;

import jp.fores.midori.server.annotation.RPCService;
import jp.fores.midori.server.config.MidoriConfigManager;
import jp.fores.midori.server.container.Container;
import jp.fores.midori.server.exception.IllegalServiceClassException;
import jp.fores.midori.server.exception.RPCTargetServiceNotFoundException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * RPCのリクエスト・レスポンスの処理を行うプラグイン全般で使う処理を集めたユーティリティクラス。<br>
 * (このクラスの全てのメソッドはスレッドセーフです。)<br>
 */
public class RPCPluginUtil {
	//==========================================================
	//定数

	/**
	 * ログ出力用
	 */
	private static final Log log = LogFactory.getLog(RPCPluginUtil.class);


	//==========================================================
	//メソッド

	//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
	//コンストラクタ

	/**
	 * コンストラクタです。<br>
	 * (クラスメソッドのみのユーティリティークラスなので、privateにしてインスタンスを作成できないようにしています。)<br>
	 */
	private RPCPluginUtil() {
		//基底クラスのコンストラクタの呼び出し
		super();
	}


	//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
	//クラスメソッド

	/**
	 * リクエストの内容を元にして、呼び出し対象のサービスクラスの完全指定のクラス名を取得します。<br>
	 * 取得できなかった場合は、nullを返します。<br>
	 *
	 * @param request リクエスト
	 * @return クラスの完全指定の名前
	 * @throws Exception 例外
	 */
	public static String getTargetClassName(HttpServletRequest request)
			throws Exception {
		//リクエストの拡張パス情報を取得
		String path = request.getPathInfo();

		//デバッグログが有効な場合
		if (log.isDebugEnabled()) {
			//デバッグログ出力
			log.debug("リクエストの拡張パス情報:" + path);
		}

		//拡張パス情報が取得出来なかった場合
		if ((path == null) || "".equals(path)) {
			//クラス名を特定できないので、nullを返す
			return null;
		}

		//拡張パス情報が「/」から始まっている場合
		if (path.startsWith("/")) {
			//先頭の「/」を取り除く
			path = path.replaceFirst("/", "");
		}


		//==========================================================
		//設定内容のルートパッケージと拡張パス情報から、呼び出し対象のサービスクラスの完全指定のクラス名を作成する

		//作業用のStringBuilderのインスタンスを生成
		StringBuilder sb = new StringBuilder();


		//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
		//ルートパッケージ

		//設定内容のルートパッケージを取得
		String rootPackage = MidoriConfigManager.getInstance().getConfig().rootPackage;

		//ルートパッケージがnull、または空文字列でない場合
		if ((rootPackage != null) && !"".equals(rootPackage)) {
			//ルートパッケージをStringBuilderに追加する
			sb.append(rootPackage);

			//次の要素との区切りための「.」をStringBuilderに追加する
			sb.append(".");
		}


		//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
		//追加パッケージ

		//拡張パス情報の最後の「/」の位置を取得
		int lastIndex = path.lastIndexOf('/');

		//最後の「/」の位置が取得できた場合
		if (lastIndex != -1) {
			//拡張パスの最後の「/」の前の部分の文字列を取得
			//(追加パッケージとして使用する)
			String addPackage = path.substring(0, lastIndex);

			//追加パッケージに含まれる「/」を「.」に全て変換する
			//(パスは「/」区切りだが、パッケージ名は「.」区切りのため)
			addPackage = addPackage.replace('/', '.');

			//追加パッケージをStringBuilderに追加する
			sb.append(addPackage);

			//次の要素との区切りための「.」をStringBuilderに追加する
			sb.append(".");
		}


		//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
		//サービスパッケージ

		//サービスパッケージをStringBuilderに追加する
		//(ここの部分は固定)
		sb.append("service");

		//次の要素との区切りための「.」をStringBuilderに追加する
		sb.append(".");


		//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
		//クラス名

		//拡張パスの最後の「/」から後の部分の文字列を取得
		//(クラス名として使用する)
		String className = path.substring(lastIndex + 1);

		//クラス名が空文字列の場合
		if ("".equals(className)) {
			//クラス名を特定できないので、nullを返す
			return null;
		}

		//クラス名の最後の「.」の位置を取得
		//(「.」の後はプラグインの特定のための拡張子部分なので、クラス名からは除外したいため)
		int dotIndex = className.lastIndexOf('.');

		//「.」の位置が先頭の場合
		//(クラス名が「.」から始まっている場合)
		if (dotIndex == 0) {
			//クラス名を特定できないので、nullを返す
			return null;
		}
		//「.」が含まれている場合
		else if (dotIndex != -1) {
			//クラス名の最後の「.」の前の部分の文字列だけを取得
			className = className.substring(0, dotIndex);
		}

		//クラス名の最初の文字を大文字にする
		className = Character.toUpperCase(className.charAt(0))
				+ className.substring(1);

		//クラス名をStringBuilderに追加する
		sb.append(className);


		//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
		//結果を文字列に変換して返す
		return sb.toString();
	}

	/**
	 * コンテナから呼び出し対象のサービスクラスのインスタンスを取得します。<br>
	 * 対象のインスタンスをコンテナから取得できなかった場合や、{@link RPCService}アノテーションが付けられていない場合は、例外を投げます。<br>
	 *
	 * @param container コンテナ
	 * @param className 呼び出し対象のサービスクラスの完全指定の名前
	 * @return 呼び出し対象のサービスクラスのインスタンス
	 * @throws RPCTargetServiceNotFoundException 対象のインスタンスをコンテナから取得できなかった場合
	 * @throws IllegalServiceClassException 対象のインスタンスに{@link RPCService}アノテーションが付けられていない場合
	 * @throws Exception 例外
	 */
	public static Object getService(Container container, String className)
			throws RPCTargetServiceNotFoundException,
			IllegalServiceClassException, Exception {
		//コンテナから、呼び出し対象のサービスクラスの完全指定の名前に対応するインスタンスを取得
		Object service = container.getObject(className);

		//インスタンスをコンテナから取得できなかった場合
		if (service == null) {
			//RPCの対象のサービスクラスが見つからなかった場合の例外を投げる
			throw new RPCTargetServiceNotFoundException(className
					+ "のインスタンスをコンテナから取得できませんでした。");
		}

		//取得したインスタンスがサービスクラスでない場合
		if (!isServiceClass(service.getClass())) {
			//サービスクラスが不正な場合の例外を投げる
			throw new IllegalServiceClassException(className
					+ "はサービスクラスとして定義されていません。");
		}

		//取得したインスタンスを返す
		return service;
	}

	/**
	 * 指定されたクラスオブジェクトがサービスクラスのものかどうかを判断します。<br>
	 *
	 * @param clazz クラスオブジェクト
	 * @return true=サービスクラス, false=サービスクラスでない
	 */
	public static boolean isServiceClass(Class clazz) {
		//インターフェースの場合
		if (clazz.isInterface()) {
			//サービスクラスと見なさないのでfalseを返す
			return false;
		}

		//抽象クラスの場合
		if ((clazz.getModifiers() & Modifier.ABSTRACT) != 0) {
			//サービスクラスと見なさないのでfalseを返す
			return false;
		}

		//引数のクラスオブジェクトにRPCServiceアノテーションが付けられていない場合
		if (clazz.getAnnotation(RPCService.class) == null) {
			//サービスクラスと見なさないのでfalseを返す
			return false;
		}

		//パッケージオブジェクトを取得
		Package packageObj = clazz.getPackage();

		//パッケージオブジェクトが取得できなかった場合、またはパッケージ名の末端が「service」でない場合
		if ((packageObj == null) || !packageObj.getName().endsWith(".service")) {
			//サービスクラスと見なさないのでfalseを返す
			return false;
		}

		//最後までチェックをくぐり抜けたので、trueを返す
		return true;
	}
}
